﻿/************************************************************************
 * AIDE - Automata Identification Engine
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     AIDE\Oracles\NFMs\SUL_MM_Cached.cs
 * Description  :     caching the queries
 * Change log   :     -Date:  Change
 * 					  -13 Aug 2014: import and export added!
 * 					  -18 Feb 2015: useFile added!
 ************************************************************************/
using System;
using System.Collections.Generic;
using AIDE.Utils;
using SMLib.Automata.Base;
using SMLib.Automata.MM;
using System.IO;


namespace AIDE.Filters.NFMs
{
	/// <summary>
	/// Cached Black Box. It provides IMPLICIT caching mechanism.
	/// </summary>
	public class Filter_MM_Cached : IBlackBox_MM
	{
		IBlackBox_MM SUT;
		Tree<PairOfSymbols> cache;
		Tree<PairOfSymbols>.TreeNode root, currentNode;
		bool foundTillhere;
		List<ISymbol> executedTraceInput;
		List<ISymbol> executedTraceOutput;
		int numberOfSULQueries, totalNumberOfQueries;
		bool justReset;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="SUT">(the black box) system under test</param>
		public Filter_MM_Cached (IBlackBox_MM SUT)
		{
			justReset = true;
			cache = new Tree<PairOfSymbols> ();
			root = cache.Root;
			currentNode = root;
			this.SUT = SUT;
			foundTillhere = true;
			executedTraceInput = new List<ISymbol> ();
			executedTraceOutput = new List<ISymbol> ();
			useFile = false;
		}

		/* this works only if inputs and outputs are StringSymbol! */
		#region -=[ IMPORT and EXPORT]=-
		StreamWriter useFilePathW;
		bool useFile;
		System.Text.StringBuilder strB;

		/// <summary>
		/// Actively uses a file
		/// </summary>
		/// <param name="path">Path of the file</param>
		public void UseFile (String path)
		{
			useFile = true;
			if (System.IO.File.Exists (path)) {
				ImportTraces (path);
				useFilePathW = File.AppendText (path); 
			} else {
				//useFilePathW = File.AppendText (path); 
				//System.IO.File.WriteAllText (path, "");
				useFilePathW = File.AppendText (path);
			}
		}

		void CommitTraceInFile (Sequence sq)
		{
			if (sq.Length == 0)
				return;

			//System.IO.File.AppendAllText (useFilePath, sq.ToString ());
			useFilePathW.WriteLine (sq.ToString ());
			useFilePathW.Flush ();
		}


		/// <summary>
		/// Exports the traces into a text file
		/// </summary>
		/// <param name="filePath">File path.</param>
		public void ExportTraces (string filePath)
		{
			strB = new System.Text.StringBuilder ();
			Sequence sq = new Sequence ();
			ExportTree (root, sq);
			System.IO.File.WriteAllText (filePath, strB.ToString ());
		}

		void ExportTree (Tree<PairOfSymbols>.TreeNode node, Sequence sq)
		{
			if (node.NumberOfChilds == 0)
				strB.AppendLine (sq.ToString ());
			else {
				for (int i=0; i<node.NumberOfChilds; i++) {
					int l = sq.Length;
					sq.AddPair (node.Edges [i]);
					ExportTree (node.Childs [i], sq);
					sq.RemovePairAt (l);
				}				
			}
		}

		/// <summary>
		/// Imports the traces from text file
		/// </summary>
		/// <param name="filePath">File path.</param>
		public void ImportTraces (string filePath)
		{
			System.IO.StreamReader sr = new System.IO.StreamReader (filePath);
			string line;
			
			while ((line=sr.ReadLine())!=null) {
				Sequence sq = Sequence.Parse (line);
				AddTrace (sq);
			}
			sr.Close ();
		}

		/// <summary>
		/// Adds one trace to the cache
		/// </summary>
		/// <returns>
		/// The true if the trace was added, false if it was repeated
		/// </returns>
		/// <param name='sq'>
		/// The input sequence
		/// </param>
		/// <exception cref='Exception'>
		/// Represents errors that occur if the trace is not consistent with the current cache
		/// </exception>
		public bool AddTrace (Sequence sq)
		{
			if (!justReset)
				throw new Exception ("Cannot a trace add before reset!");
			
			bool tmpFoundTillhere = true;
			Tree<PairOfSymbols>.TreeNode tmpCurrentNode = root;
			for (int i=0; i<sq.Length; i++) {
				
				if (tmpFoundTillhere) {
					bool found = false;
					foreach (PairOfSymbols n in tmpCurrentNode.Edges) {
						if (n.Input.Equals (sq [i].Input)) {
							tmpCurrentNode = tmpCurrentNode.ChildByEdge (n);
							found = true;
                        
							if (!n.Output.Equals (sq [i].Output))
								throw new Exception ("Inconsistency in execution traces!");
							else
								break;
						}
					}
					if (found)
						continue;
					tmpFoundTillhere = false;
					i--;
				} else {
					tmpCurrentNode = tmpCurrentNode.AddChild (sq [i]);
				}				
			}
			return tmpFoundTillhere;
		}
		#endregion

		/// <summary>
		/// Gets the number of saved queries.
		/// </summary>
		/// <value>
		/// The number of saved queries.
		/// </value>
		public int NumberOfCacheHit {
			get {
				return totalNumberOfQueries - numberOfSULQueries;
			}
		}

		/// <summary>
		/// Gets the total number of queries.
		/// </summary>
		/// <value>
		/// The total number of queries.
		/// </value>
		public int TotalNumberOfQueries {
			get {
				return totalNumberOfQueries;
			}
		}

		#region IBlackBox_MealyMachine Members
		public void Reset ()
		{
			if (!foundTillhere && useFile) {
				Sequence sq = new Sequence ();
				for (int i=0; i<executedTraceInput.Count; i++) {
					sq.AddPair (executedTraceInput [i], executedTraceOutput [i]);
				}
				CommitTraceInFile (sq);
			}

			totalNumberOfQueries++;
			currentNode = root;
			foundTillhere = true;
			executedTraceInput.Clear ();
			executedTraceOutput.Clear ();

			justReset = true;
		}

		public ISymbol Step (ISymbol input)
		{
			justReset = false;
			ISymbol output;
			if (foundTillhere) {
				foreach (PairOfSymbols n in currentNode.Edges) {
					if (n.Input.Equals (input)) {
						executedTraceInput.Add (input);
						currentNode = currentNode.ChildByEdge (n); 
						executedTraceOutput.Add (n.Output);
						return n.Output;
					}
				}
				//* Oops! not found.
				foundTillhere = false;
				SUT.Reset ();
				numberOfSULQueries++;
				for (int i = 0; i < executedTraceInput.Count; i++) {
					SUT.Step (executedTraceInput [i]);
				}
				output = SUT.Step (input);
				executedTraceInput.Add (input);
				executedTraceOutput.Add (output);
				currentNode = currentNode.AddChild (new PairOfSymbols (input, output));
				return output;
			} else {
				output = SUT.Step (input);

				executedTraceInput.Add (input);
				executedTraceOutput.Add (output);

				currentNode = currentNode.AddChild (new PairOfSymbols (input, output));
				return output;
			}
		}

		public Alphabet InputAlphabet {
			get { return SUT.InputAlphabet; }
		}
		#endregion
	}
}
